package sms

import (
	"chatgpt/helper"
	"chatgpt/logq"
	"chatgpt/services/datasrv/cache"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	sms_code_key  = "code"
	sms_valid_key = "valid"
	sms_duration  = 60 //60秒发送一次
)

type SMS struct {
	codeCache *sync.Map //k:str_cell v:n_code
}

type CodeData struct {
	Value   string
	Expires time.Time
}

var smsInstance *SMS

func GetInstance() *SMS {
	return smsInstance
}

func init() {
	smsInstance = new(SMS)
	smsInstance.codeCache = new(sync.Map)
}

func NewVerifyCode(lenth int) string {
	rand.Seed(time.Now().UnixNano())

	var num [10]byte
	for i := 0; i < 10; i++ {
		num[i] = byte(i)
	}

	var newVCode strings.Builder
	for i := 0; i < lenth; i++ {
		fmt.Fprintf(&newVCode, "%d", num[rand.Intn(len(num))])
	}
	return newVCode.String()
}

func (sms *SMS) GetVerifyCode(cell int64) error {
	cnf := helper.Config().Sub("SMS")
	if value := cache.Get(sms.GetCacheKey(cell) + "-ttl"); value == "exist" {
		return errors.New("verify code request too many times!!!")
	}

	return sms.GetVerifyCodeRemote(cell, func() {
		cache.Set(sms.GetCacheKey(cell)+"-ttl", "exist", time.Second*sms_duration)
	}, func(smscode string) {
		expire := time.Minute * time.Duration(cnf.GetInt("time_to_live"))
		//ctx := context.Background()
		//redis.Redis().Set(context.Background(), sms.GetCacheKey(cell), smsCode, expire).Result()
		cache.HSet(sms.GetCacheKey(cell), sms_code_key, smscode, expire)
	})
}

func (sms *SMS) GetVerifyCodeRemote(cell int64, valid_fn func(), cache_fn func(code string)) error {
	logq.DebugF("TimeNowUnix [%v]", time.Now().Unix())
	cnf := helper.Config().Sub("SMS")
	smsCode := NewVerifyCode(cnf.GetInt("code_length"))
	urlValue := url.Values{}
	urlValue["AccessKeyId"] = []string{cnf.GetString("accesskey_id")}
	urlValue["Action"] = []string{"SendSms"}
	urlValue["Format"] = []string{"JSON"}
	urlValue["PhoneNumbers"] = []string{strconv.FormatInt(cell, 10)}
	urlValue["RegionId"] = []string{"cn-hangzhou"}
	urlValue["SignName"] = []string{cnf.GetString("sign_name")}
	urlValue["SignatureMethod"] = []string{"HMAC-SHA1"}
	urlValue["SignatureNonce"] = []string{uuid.New().String()}
	urlValue["SignatureVersion"] = []string{"1.0"}
	urlValue["TemplateCode"] = []string{cnf.GetString("template_code")}
	urlValue["TemplateParam"] = []string{fmt.Sprintf("{\"code\": \"%s\"}", smsCode)}
	urlValue["Timestamp"] = []string{helper.GetDateUTC()}
	urlValue["Version"] = []string{"2017-05-25"}
	strSignature := sms.Signature(urlValue)
	urlValue["Signature"] = []string{strSignature}

	fmt.Println("--------------------------------------------------------------------")
	iInMap := 0
	for k, v := range urlValue {
		logq.DebugF("[%v] [%v] =\t\t[%v]", iInMap, k, v)
		iInMap++
	}
	fmt.Println("--------------------------------------------------------------------")

	// 手机请求过一次验证码
	defer valid_fn()
	response, err := http.PostForm(cnf.GetString("url"), urlValue)
	if err != nil {
		logq.ErrorF("SMS Request失败，原因:%v", err)
		return err
	}
	defer response.Body.Close()

	logq.DebugF("SMS http status[%v] statuscode[%v]", response.Status, response.StatusCode)

	responseBody, errResp := ioutil.ReadAll(response.Body)
	if errResp != nil {
		// handle error
		logq.Debug("SMS onResponse response read error! ")
		return errResp
	}
	fmt.Println()
	fmt.Println("---------------- response body ----------------")
	fmt.Println("response-body[" + string(responseBody) + "]")
	fmt.Println("-----------------------------------------------")

	ret := map[string]interface{}{}
	err = json.Unmarshal(responseBody, &ret)
	if err != nil {
		return err
	}
	if result, ok := ret["Code"]; ok {
		if result.(string) != "OK" {
			//if _, msgOk := ret["Message"]; !msgOk {
			//	return errors.New("验证码发送失败")
			//} else {
			//	return nil
			//}
			return errors.New(result.(string))
		} else {
			logq.DebugF("[%v]发送验证码[%v]成功", cell, smsCode)

			//sms.codeCache.Store(cell, CodeData{
			//	Value:   smsCode,
			//	Expires: time.Now().Add(time.Minute * time.Duration(conf.WebConf.SMS.TTL)),
			//})
			//expire := time.Duration(time.Now().Add(time.Minute * time.Duration(conf.WebConf.SMS.TTL)).UnixNano())
			cache_fn(smsCode)
			return nil
		}
	} else {
		return errors.New(string(responseBody))
	}
}

func (sms *SMS) GetCacheKey(cell interface{}) string {
	switch cell.(type) {
	case string:
		v, _ := cell.(string)
		return "v_code:" + v
	case int64:
		v, _ := cell.(int64)
		return "v_code:" + strconv.FormatInt(int64(v), 10)
	case int:
		v, _ := cell.(int)
		return "v_code:" + strconv.FormatInt(int64(v), 10)
	}
	return ""
}

func (sms *SMS) CheckVerifyCode(cell int64, code string) bool {
	cnf := helper.Config().Sub("SMS")
	if value := cache.HGet(sms.GetCacheKey(cell), sms_code_key); code == value {
		cache.HSet(sms.GetCacheKey(cell), sms_valid_key, "yes", time.Minute*time.Duration(cnf.GetInt("time_to_live")))
		return true
	}
	return false
}

// 注册时检查电话是否合法
func (sms *SMS) CheckCellRegister(cell string) bool {
	if value := cache.HGet(sms.GetCacheKey(cell), sms_valid_key); value == "yes" {

		return true
	}
	return false
}

func (sms *SMS) Signature(urlValue url.Values) string {
	urlValueKeys := make([]string, 0)
	for k, _ := range urlValue {
		urlValueKeys = append(urlValueKeys, k)
	}
	sort.Strings(urlValueKeys)
	cnf := helper.Config().Sub("SMS")
	var sortQueryString string
	for i, v := range urlValueKeys {
		sortQueryString += "&" + specialUrlEncode(urlValueKeys[i]) + "=" + specialUrlEncode(urlValue[v][0])
	}
	sortQueryString = strings.TrimLeft(sortQueryString, "&")

	stringToSign := "POST&" + specialUrlEncode("/") + "&" + specialUrlEncode(sortQueryString)

	hmacSign := helper.HmacSHA1UTF8(stringToSign, cnf.GetString("accesskey_secret")+"&")

	return hmacSign
}

func specialUrlEncode(value string) string {
	value = url.QueryEscape(value)
	value = strings.Replace(value, "+", "%20", -1)
	value = strings.Replace(value, "*", "%2A", -1)
	value = strings.Replace(value, "%7E", "~", -1)
	return value
}
